<!-- RAG Search Tab -->
<div id="tabRAGSearch" class="tab-content">
    <div class="endpoint-section" id="ragGlobalPresets">
        <h2>RAG Global Presets</h2>
        <p>Load a server preset and apply to both Simple and Complex RAG forms.</p>
        <div class="form-group">
            <button class="api-button" id="btnRagGlobalRefresh">Refresh Presets</button>
            <select id="ragGlobalPreset_select" style="min-width: 260px;"></select>
            <button class="api-button" id="btnRagGlobalApply">Apply to Both Forms</button>
        </div>
        <script src="js/rag.js"></script>
    </div>

    <div class="endpoint-section" id="ragSelector">
        <h2>RAG Item Selector</h2>
        <p>Select a category → keyword → items, then add items to your query without re-searching.</p>
        <p style="font-size: 0.9em; color: #666;">
            Tip: Added items are injected into the Complex RAG request as <code>include_media_ids</code> and/or
            <code>include_note_ids</code>. Retrieval will be restricted to these items only.
        </p>
        <div class="form-group">
            <label for="ragSel_category">Category:</label>
            <select id="ragSel_category" style="min-width: 280px;"></select>
            <button class="api-button" id="btnRagSelInit">Refresh</button>
        </div>
        <div class="form-group">
            <label for="ragSel_keywords">Keyword:</label>
            <select id="ragSel_keywords" style="min-width: 280px;"></select>
            <input id="ragSel_keyword_text" placeholder="Enter keyword (media)" style="min-width: 280px; display:none;" />
            <div style="margin-top:6px;">
                <button class="api-button" id="btnRagSelLoadKeywords">Load Keywords</button>
                <button class="api-button" id="btnRagSelLoadItems">Load Items</button>
            </div>
        </div>

        <div class="form-group">
            <label>Items:</label>
            <div id="ragSel_items" style="max-height: 220px; overflow:auto; border:1px solid #ddd; padding:6px;"></div>
            <button class="api-button" id="btnRagSelAddSelected">Add Selected</button>
        </div>

        <div class="form-group">
            <label>Selected Items:</label>
            <div id="ragSel_selected" style="min-height: 40px; border:1px dashed #aaa; padding:6px;"></div>
            <div style="margin-top:6px;">
                <button class="api-button" id="btnRagSelApplyToComplex">Apply to Complex JSON</button>
                <button class="api-button" id="btnRagSelClear">Clear</button>
            </div>
        </div>

        <!-- migrated to js/rag.js
        <script>
            const ragSelState = { media_ids: new Set(), note_ids: new Set() };

            async function ragSelInitSources() {
                const sel = document.getElementById('ragSel_category');
                sel.innerHTML = '';
                const opts = [
                    { value: 'notes', label: 'Notes Keywords' },
                    { value: 'media', label: 'Media Keywords' }
                ];
                opts.forEach(o => { const opt = document.createElement('option'); opt.value = o.value; opt.textContent = o.label; sel.appendChild(opt); });
                ragSelToggleKeywordInput();
                Toast.success('Sources loaded');
            }

            function ragSelToggleKeywordInput() {
                const cat = document.getElementById('ragSel_category')?.value || 'notes';
                const sel = document.getElementById('ragSel_keywords');
                const input = document.getElementById('ragSel_keyword_text');
                if (cat === 'notes') { sel.style.display = ''; input.style.display = 'none'; }
                else { sel.style.display = 'none'; input.style.display = ''; }
            }

            async function ragSelLoadKeywords() {
                const cat = document.getElementById('ragSel_category')?.value || 'notes';
                if (cat !== 'notes') { ragSelToggleKeywordInput(); return; }
                try {
                    const resp = await apiClient.makeRequest('GET', `/api/v1/notes/keywords/?limit=500&offset=0`);
                    const sel = document.getElementById('ragSel_keywords');
                    sel.innerHTML = '';
                    (resp || []).forEach(k => {
                        const opt = document.createElement('option');
                        opt.value = k.id; opt.textContent = k.keyword; sel.appendChild(opt);
                    });
                    Toast.success(`Loaded ${ (resp||[]).length } keywords`);
                } catch (e) {
                    Toast.error('Failed to load keywords: ' + (e?.message || e));
                }
            }



            function ragSelRenderSelected() {
                const el = document.getElementById('ragSel_selected');
                const media = Array.from(ragSelState.media_ids);
                const notes = Array.from(ragSelState.note_ids);
                el.textContent = `media_ids=[${media.join(', ')}]; note_ids=[${notes.join(', ')}]`;
            }

            function ragSelAddSelected() {
                const cont = document.getElementById('ragSel_items');
                const boxes = cont.querySelectorAll('input[type="checkbox"]:checked');
                let added = 0;
                boxes.forEach(b => {
                    const src = b.getAttribute('data-source');
                    const id = b.getAttribute('data-id');
                    if (src === 'media_db') { ragSelState.media_ids.add(parseInt(id, 10)); added++; }
                    else if (src === 'notes') { ragSelState.note_ids.add(String(id)); added++; }
                });
                ragSelRenderSelected();
                Toast.success(`Added ${added} items`);
            }

            function ragSelClear() {
                ragSelState.media_ids.clear();
                ragSelState.note_ids.clear();
                ragSelRenderSelected();
            }

            function ragSelApplyToComplex() {
                try {
                    const ta = document.getElementById('ragComplexSearch_payload');
                    let obj = {};
                    try { obj = JSON.parse(ta.value); } catch (e) { obj = {}; }
                    const media = Array.from(ragSelState.media_ids);
                    const notes = Array.from(ragSelState.note_ids);
                    if (media.length) obj.include_media_ids = media;
                    if (notes.length) obj.include_note_ids = notes;
                    ta.value = JSON.stringify(obj, null, 2);
                    Toast.success('Applied selected items to Complex JSON');
                } catch (e) {
                    Toast.error('Failed to apply: ' + (e?.message || e));
                }
            }

            // Helper using existing endpoints to fetch items for Notes/Media
            async function ragSelLoadItems2() {
                const cat = document.getElementById('ragSel_category')?.value || 'notes';
                const cont = document.getElementById('ragSel_items');
                cont.innerHTML = '';
                try {
                    if (cat === 'notes') {
                        const kid = document.getElementById('ragSel_keywords')?.value;
                        if (!kid) { alert('Select a keyword'); return; }
                        const resp = await apiClient.makeRequest('GET', `/api/v1/notes/keywords/${kid}/notes/?limit=50&offset=0`);
                        const items = (resp?.notes || []).map(n => ({ source: 'notes', id: n.id, title: n.title }));
                        items.forEach(item => {
                            const div = document.createElement('div');
                            div.style.marginBottom = '4px';
                            const label = document.createElement('label');
                            const input = document.createElement('input');
                            input.type = 'checkbox';
                            input.setAttribute('data-source', String(item.source));
                            input.setAttribute('data-id', String(item.id));
                            label.appendChild(input);
                            label.appendChild(document.createTextNode(` [${String(item.source)}] ${String(item.title || item.id)}`));
                            div.appendChild(label);
                            cont.appendChild(div);
                        });
                        Toast.success(`Loaded ${items.length} notes items`);
                    } else {
                        const kw = (document.getElementById('ragSel_keyword_text')?.value || '').trim();
                        if (!kw) { alert('Enter a keyword for media'); return; }
                        const body = { query: null, fields: ["title","content"], must_have: [kw], sort_by: 'relevance' };
                        const resp = await apiClient.makeRequest('POST', `/api/v1/media/search?page=1&results_per_page=50`, body);
                        const items = (resp?.items || resp?.results || resp?.data || []).map(m => ({ source: 'media_db', id: m.id || m.media_id || m['id'], title: m.title || m['title'] }));
                        items.forEach(item => {
                            const div = document.createElement('div');
                            div.style.marginBottom = '4px';
                            const label = document.createElement('label');
                            const input = document.createElement('input');
                            input.type = 'checkbox';
                            input.setAttribute('data-source', String(item.source));
                            input.setAttribute('data-id', String(item.id));
                            label.appendChild(input);
                            label.appendChild(document.createTextNode(` [${String(item.source)}] ${String(item.title || item.id)}`));
                            div.appendChild(label);
                            cont.appendChild(div);
                        });
                        Toast.success(`Loaded ${items.length} media items for '${kw}'`);
                    }
                } catch (e) {
                    Toast.error('Failed to load items: ' + (e?.message || e));
                }
            }

            // Auto-init on tab load
            (function(){ try { ragSelInitSources(); ragSelLoadKeywords(); } catch(e){} })();
            document.getElementById('ragSel_category')?.addEventListener('change', () => { ragSelToggleKeywordInput(); ragSelLoadKeywords(); });
        </script> -->
    </div>
    <div class="endpoint-section" id="ragSimpleSearch">
        <h2>
            <span class="endpoint-method post">POST</span>
            <span class="endpoint-path">/api/v1/rag/search - Unified RAG Search</span>
        </h2>
        <p>Simple RAG search with essential parameters. Supports multi-database search, automatic context limiting, optional reranking, and keyword filtering.</p>

        <div class="form-group">
            <label for="ragSimpleSearch_query">Query <span class="required">*</span>:</label>
            <textarea id="ragSimpleSearch_query" rows="3" placeholder="Enter your search query...">What are the key features of the system?</textarea>
        </div>

        <div class="columns">
            <div class="column">
                <div class="form-group">
                    <label for="ragSimpleSearch_search_type">Search Type:</label>
                    <select id="ragSimpleSearch_search_type">
                        <option value="hybrid" selected>Hybrid (BM25 + Vector)</option>
                        <option value="semantic">Semantic (Vector Only)</option>
                        <option value="fulltext">Full Text (BM25 Only)</option>
                    </select>
                </div>

                <div class="form-group">
                    <label for="ragSimpleSearch_limit">Limit:</label>
                    <input type="number" id="ragSimpleSearch_limit" value="10" min="1" max="100">
                </div>
            </div>

            <div class="column">
                <div class="form-group">
                    <label for="ragSimpleSearch_collection_name">Collection Name:</label>
                    <input type="text" id="ragSimpleSearch_collection_name" placeholder="media_db (optional)">
                </div>

                <div class="form-group">
                    <label for="ragSimpleSearch_threshold">Score Threshold:</label>
                    <input type="number" id="ragSimpleSearch_threshold" value="0.5" step="0.1" min="0" max="1">
                </div>
            </div>
        </div>

        <div class="columns">
            <div class="column">
                <div class="form-group">
                    <label for="ragSimpleSearch_corpus">Corpus/Namespace (optional):</label>
                    <input type="text" id="ragSimpleSearch_corpus" placeholder="e.g., my_corpus">
                </div>
            </div>
            <div class="column">
                <div class="form-group">
                    <label>&nbsp;</label>
                    <button class="api-button" id="btnRagSimpleSend">Send Request</button>
                </div>
            </div>
        </div>
        <div class="form-group" style="margin: 10px 0; padding: 10px; border: 1px dashed #ccc;">
          <h4>Reranking (Unified)</h4>
          <div class="columns">
            <div class="column">
              <label><input type="checkbox" id="ragSimple_enableRerank" checked> Enable Reranking</label>
            </div>
            <div class="column">
              <label for="ragSimple_rerankStrategy">Strategy:</label>
              <select id="ragSimple_rerankStrategy">
                <option value="flashrank">flashrank</option>
                <option value="cross_encoder">cross_encoder</option>
                <option value="hybrid">hybrid</option>
                <option value="llama_cpp">llama_cpp</option>
                <option value="llm_scoring">llm_scoring</option>
                <option value="two_tier">two_tier</option>
              </select>
            </div>
            <div class="column">
              <label for="ragSimple_rerankTopK">Top K:</label>
              <input type="number" id="ragSimple_rerankTopK" min="1" max="50" placeholder="e.g., 10" />
            </div>
          </div>
          <small class="text-muted">These add <code>enable_reranking</code>, <code>reranking_strategy</code>, <code>rerank_top_k</code> to the request.</small>
        </div>
        <div class="form-group" style="margin: 10px 0; padding: 10px; border: 1px dashed #ccc;">
          <h4>VLM Late-Chunking (Simple)</h4>
          <div class="columns">
            <div class="column"><label><input type="checkbox" id="ragSimple_enableVlmLC"> Enable</label></div>
            <div class="column">
              <label for="ragSimple_vlmBackend">Backend:</label>
              <select id="ragSimple_vlmBackend"><option value="">(auto)</option></select>
            </div>
            <div class="column"><label><input type="checkbox" id="ragSimple_vlmDetectTablesOnly" checked> Tables only</label></div>
            <div class="column"><label for="ragSimple_vlmMaxPages">Max pages</label><input type="number" id="ragSimple_vlmMaxPages" min="1" max="200" placeholder="20" /></div>
          </div>
          <div class="form-group" style="margin-top:6px;">
            <button class="api-button" id="btnRagSimpleVlmLoadFromCaps">Load Backends from Capabilities</button>
          </div>
        </div>
        <textarea id="ragSimpleSearch_payload" style="display:none"></textarea>

        <h4>Server Presets</h4>
        <div class="form-group">
            <button class="api-button" id="btnRagSimpleRefreshPresets">Refresh Presets</button>
            <select id="ragSimplePreset_select" style="min-width: 240px;"></select>
            <button class="api-button" id="btnRagSimpleApplyPreset">Apply Selected Preset</button>
        </div>
        <!-- migrated to js/rag.js
        <script>
            async function refreshSimpleRagPresets() {
                try {
                    const resp = await apiClient.makeRequest('GET', '/api/v1/evaluations/rag/pipeline/presets');
                    const sel = document.getElementById('ragSimplePreset_select');
                    sel.innerHTML = '';
                    const items = (resp && resp.items) ? resp.items : [];
                    items.forEach(item => {
                        const opt = document.createElement('option');
                        opt.value = item.name; opt.textContent = item.name;
                        sel.appendChild(opt);
                    });
                    Toast.success(`Loaded ${items.length} presets`);
                } catch (e) {
                    alert('Failed to list presets: ' + (e?.message || e));
                }
            }
            async function applySimpleRagPreset() {
                try {
                    const sel = document.getElementById('ragSimplePreset_select');
                    const name = sel && sel.value ? sel.value : null;
                    if (!name) { alert('Select a preset first'); return; }
                    const resp = await apiClient.makeRequest('GET', `/api/v1/evaluations/rag/pipeline/presets/${encodeURIComponent(name)}`);
                    if (!(resp && resp.config)) { alert('Preset not found'); return; }
                    const cfg = resp.config;
                    // Map to simple UI controls
                    if (cfg.retriever) {
                        const sm = cfg.retriever.search_mode || cfg.retriever.search_type;
                        if (sm) {
                            const selType = document.getElementById('ragSimpleSearch_search_type');
                            const norm = (sm === 'vector') ? 'semantic' : (sm === 'fts' ? 'fulltext' : 'hybrid');
                            selType.value = norm;
                        }
                        if (cfg.retriever.top_k !== undefined) {
                            document.getElementById('ragSimpleSearch_limit').value = cfg.retriever.top_k;
                        }
                    }
                    Toast.success('Preset applied to simple search controls.');
                } catch (e) {
                    alert('Failed to apply preset: ' + (e?.message || e));
                }
            }

            // Keep Complex form in sync with Simple controls
            function updateComplexFromSimple() {
                try {
                    const ta = document.getElementById('ragComplexSearch_payload');
                    if (!ta) return;
                    let obj = {};
                    try { obj = JSON.parse(ta.value); } catch (e) { obj = {}; }
                    obj.retrieval = obj.retrieval || { search_type: 'hybrid', hybrid_config: {}, vector_config: {}, fts_config: {} };

                    // Map search type
                    const st = (document.getElementById('ragSimpleSearch_search_type')?.value || 'hybrid');
                    obj.retrieval.search_type = st;
                    if (st === 'semantic') obj.retrieval.search_type = 'semantic';
                    else if (st === 'fulltext') obj.retrieval.search_type = 'fulltext';
                    else obj.retrieval.search_type = 'hybrid';

                    // Map limit to vector_config.top_k (most impactful for retrieval)
                    const limStr = (document.getElementById('ragSimpleSearch_limit')?.value || '10');
                    const lim = parseInt(limStr, 10);
                    if (!isNaN(lim) && lim > 0) {
                        obj.retrieval.vector_config = obj.retrieval.vector_config || {};
                        obj.retrieval.vector_config.top_k = lim;
                    }

                    // Map score threshold to a conservative place if present
                    const thresholdStr = (document.getElementById('ragSimpleSearch_threshold')?.value || '0');
                    const thr = parseFloat(thresholdStr);
                    if (!isNaN(thr) && thr >= 0 && thr <= 1) {
                        obj.retrieval.min_score = thr;
                    }

                    ta.value = JSON.stringify(obj, null, 2);
                } catch (e) {
                    console.warn('Failed to sync complex form:', e?.message || e);
                }
            }

            // Attach listeners once DOM for this tab is available
            (function attachSimpleSyncListeners() {
                try {
                    const st = document.getElementById('ragSimpleSearch_search_type');
                    const lim = document.getElementById('ragSimpleSearch_limit');
                    const thr = document.getElementById('ragSimpleSearch_threshold');
                    if (st) st.addEventListener('change', updateComplexFromSimple);
                    if (lim) lim.addEventListener('change', updateComplexFromSimple);
                    if (thr) thr.addEventListener('change', updateComplexFromSimple);
                } catch (e) {
                    // ignore
                }
            })();

            // Build minimal unified RAG payload from Simple controls and send
            function ragSimpleApplyAndSend() {
                try {
                    const q = (document.getElementById('ragSimpleSearch_query')?.value || '').trim();
                    if (!q) { alert('Enter a query'); return; }
                    const st = (document.getElementById('ragSimpleSearch_search_type')?.value || 'hybrid');
                    const limStr = (document.getElementById('ragSimpleSearch_limit')?.value || '10');
                    const thrStr = (document.getElementById('ragSimpleSearch_threshold')?.value || '0');
                    const corpus = (document.getElementById('ragSimpleSearch_corpus')?.value || '').trim();

                    const topK = parseInt(limStr, 10);
                    const minScore = parseFloat(thrStr);
                    const searchMode = (st === 'semantic') ? 'vector' : ((st === 'fulltext') ? 'fts' : 'hybrid');

                    const payload = {
                        query: q,
                        search_mode: searchMode,
                        ...(topK > 0 ? { top_k: topK } : {}),
                        ...(minScore >= 0 && minScore <= 1 ? { min_score: minScore } : {}),
                        ...(corpus ? { corpus, index_namespace: corpus } : {})
                    };
                    const hidden = document.getElementById('ragSimpleSearch_payload');
                    hidden.value = JSON.stringify(payload, null, 2);
                    // Use generic helper
                    makeRequest('ragSimpleSearch', 'POST', '/api/v1/rag/search', 'json');
                } catch (e) {
                    Toast.error('Failed to build request: ' + (e?.message || e));
                }
            }
        </script> -->

        <h3>cURL Command:</h3>
        <pre id="ragSimpleSearch_curl">---</pre>

        <h3>Response:</h3>
        <pre id="ragSimpleSearch_response">---</pre>
        <div class="form-group" style="margin-top:8px; display:flex; gap:8px; align-items:center;">
            <button class="api-button" id="btnRagSimpleShowExplain">Show Explain (Agentic)</button>
            <span class="text-muted">Renders plan + spans when strategy=agentic (or provenance present)</span>
        </div>
        <div id="ragSimpleSearch_explainPanel" class="mt-2"></div>
        <div id="ragSimpleSearch_resultList" class="mt-2"></div>
    </div>

    <!-- Streaming Preview: NDJSON events (contexts + reasoning + deltas) -->
    <div class="endpoint-section" id="ragStreamingPreview">
        <h2>
            <span class="endpoint-method post">POST</span>
            <span class="endpoint-path">/api/v1/rag/search/stream - Streaming Preview</span>
        </h2>
        <p>Preview the initial <strong>contexts</strong> and a lightweight <strong>why</strong> summary in real time (NDJSON). Uses the Simple form’s query/type/limit.</p>
        <div class="form-group">
            <button class="api-button" id="ragStream_start">Start Streaming Preview</button>
            <button class="api-button" id="ragStream_stop" disabled>Stop</button>
            <label style="margin-left:8px; font-size:0.95em;"><input type="checkbox" id="ragStream_autoClear" checked> Auto-clear on start</label>
            <button class="api-button" id="ragStream_clear">Clear</button>
            <button class="api-button" id="ragStream_copyWhy">Copy Why</button>
            <button class="api-button" id="ragStream_copyAnswer">Copy Answer</button>
        </div>
        <div class="columns">
            <div class="column">
                <h3>Contexts</h3>
                <pre id="ragStream_contexts">(none)</pre>
            </div>
            <div class="column">
                <h3>Why</h3>
                <pre id="ragStream_why">(none)</pre>
            </div>
            <div class="column">
                <h3>Answer</h3>
                <pre id="ragStream_answer">(none)</pre>
            </div>
        </div>
        <!-- migrated to js/rag.js
        <script>
            let ragStreamAbort = null;
            let ragStreamActive = false;
            function _ragAuthHeaders(hdr = {}) {
                try {
                    if (!apiClient || !apiClient.token) return hdr;
                    if (apiClient.authMode === 'single-user' || apiClient.preferApiKeyInMultiUser) {
                        hdr['X-API-KEY'] = apiClient.token;
                    } else {
                        hdr['Authorization'] = `Bearer ${apiClient.token}`;
                    }
                } catch (e) {}
                return hdr;
            }
            async function startRagStreaming() {
                try {
                    if (ragStreamActive) return;
                    const btnStart = document.getElementById('ragStream_start');
                    const btnStop = document.getElementById('ragStream_stop');
                    const preC = document.getElementById('ragStream_contexts');
                    const preW = document.getElementById('ragStream_why');
                    preC.textContent = '(waiting...)';
                    preW.textContent = '(waiting...)';
                    btnStart.disabled = true; btnStop.disabled = false;
                    ragStreamActive = true;

                    // Build payload from Simple controls
                    const q = (document.getElementById('ragSimpleSearch_query')?.value || '').trim();
                    const st = (document.getElementById('ragSimpleSearch_search_type')?.value || 'hybrid');
                    const limStr = (document.getElementById('ragSimpleSearch_limit')?.value || '10');
                    const topK = parseInt(limStr, 10);
                    const searchMode = (st === 'semantic') ? 'vector' : ((st === 'fulltext') ? 'fts' : 'hybrid');
                    const payload = {
                        query: q,
                        search_mode: searchMode,
                        top_k: isNaN(topK) ? 10 : topK,
                        enable_generation: true,
                        enable_claims: false
                    };

                    // Fire NDJSON request
                    const ctrl = new AbortController();
                    ragStreamAbort = ctrl;
                    const url = `${apiClient.baseUrl}/api/v1/rag/search/stream`;
                    const headers = _ragAuthHeaders({ 'Content-Type': 'application/json', 'Accept': 'application/x-ndjson' });
                    const resp = await fetch(url, { method: 'POST', headers, body: JSON.stringify(payload), signal: ctrl.signal });
                    if (!resp.ok || !resp.body) { throw new Error(`HTTP ${resp.status}`); }
                    const reader = resp.body.getReader();
                    const decoder = new TextDecoder();
                    let buffer = '';
                    const _update = (evt) => {
                        try {
                            if (evt.type === 'contexts') {
                                const list = Array.isArray(evt.contexts) ? evt.contexts : [];
                                preC.textContent = JSON.stringify(list, null, 2);
                                if (evt.why) preW.textContent = JSON.stringify(evt.why, null, 2);
                            } else if (evt.type === 'reasoning') {
                                // Optional: display the plan under Why
                                const cur = preW.textContent.trim();
                                const plan = { plan: evt.plan || [] };
                                preW.textContent = (cur === '(waiting...)' || !cur) ? JSON.stringify(plan, null, 2)
                                    : (cur + '\n' + JSON.stringify(plan, null, 2));
                            }
                        } catch (e) { /* ignore */ }
                    };
                    while (ragStreamActive) {
                        const { value, done } = await reader.read();
                        if (done) break;
                        buffer += decoder.decode(value, { stream: true });
                        const lines = buffer.split('\n');
                        buffer = lines.pop() || '';
                        for (const line of lines) {
                            const trimmed = (line || '').trim();
                            if (!trimmed) continue;
                            try { const evt = JSON.parse(trimmed); _update(evt); } catch (e) { /* ignore parse errors */ }
                        }
                    }
                } catch (e) {
                    console.warn('stream error', e?.message || e);
                } finally {
                    ragStreamActive = false;
                    const btnStart = document.getElementById('ragStream_start');
                    const btnStop = document.getElementById('ragStream_stop');
                    btnStart.disabled = false; btnStop.disabled = true;
                }
            }
            function stopRagStreaming() {
                try { if (ragStreamAbort) ragStreamAbort.abort(); } catch (e) {}
                ragStreamActive = false;
                const btnStart = document.getElementById('ragStream_start');
                const btnStop = document.getElementById('ragStream_stop');
                btnStart.disabled = false; btnStop.disabled = true;
            }
        </script> -->
    </div>

        <div class="endpoint-section" id="ragComplexSearch">
        <h2>
            <span class="endpoint-method post">POST</span>
            <span class="endpoint-path">/api/v1/rag/search - Unified RAG Search</span>
        </h2>
        <p>Complex RAG search with complete configurability. Full control over all pipeline components, per-database configuration, advanced retrieval options, multiple reranking strategies, and comprehensive filtering.</p>

        <div class="form-group" style="margin: 10px 0; padding: 10px; border: 1px dashed #ccc;">
          <h4>VLM Late-Chunking</h4>
          <div class="columns">
            <div class="column"><label><input type="checkbox" id="ragComplex_enableVlmLC"> Enable</label></div>
            <div class="column">
              <label for="ragComplex_vlmBackend">Backend:</label>
              <select id="ragComplex_vlmBackend"><option value="">(auto)</option></select>
            </div>
            <div class="column"><label><input type="checkbox" id="ragComplex_vlmDetectTablesOnly" checked> Tables only</label></div>
            <div class="column"><label for="ragComplex_vlmMaxPages">Max pages</label><input type="number" id="ragComplex_vlmMaxPages" min="1" max="200" placeholder="20" /></div>
          </div>
          <div class="form-group" style="margin-top:6px;">
            <button class="api-button" id="btnRagVlmLoadFromCaps">Load Backends from Capabilities</button>
            <small class="text-muted" style="margin-left:8px;">Backends and defaults via Capabilities; uses /rag/vlm/backends for availability.</small>
          </div>
        </div>
        <div class="form-group">
            <label for="ragComplexSearch_payload">Request Payload (JSON):</label>
            <textarea id="ragComplexSearch_payload" class="code-input" rows="25">{
  "query": "Explain the architecture and design patterns",
  "databases": {
    "media_db": {
      "weight": 0.7,
      "search_type": "hybrid",
      "limit": 20
    },
    "notes": {
      "weight": 0.3,
      "search_type": "semantic"
    }
  },
  "retrieval": {
    "search_type": "hybrid",
    "hybrid_config": {
      "alpha": 0.7,
      "normalize_scores": true
    },
    "vector_config": {
      "model": "all-MiniLM-L6-v2",
      "top_k": 50
    },
    "fts_config": {
      "query_expansion": true,
      "fuzzy_matching": true
    }
  },
  "reranking": {
    "enabled": true,
    "strategies": ["similarity", "diversity"],
    "model": "cross-encoder/ms-marco-MiniLM-L-12-v2",
    "top_k": 10
  },
  "quick_wins": {
    "spell_check": {"enabled": true},
    "highlight_results": {"enabled": true},
    "cost_tracking": {"enabled": true}
  },
  "pipeline_config": {
    "preset": "quality"
  }
}</textarea>
        </div>

        <div class="columns">
          <div class="column">
            <div class="form-group">
              <label for="ragComplexSearch_corpus">Corpus/Namespace (optional):</label>
              <input type="text" id="ragComplexSearch_corpus" placeholder="e.g., my_corpus">
            </div>
          </div>
          <div class="column">
            <div class="form-group">
              <button class="api-button" id="btnRagApplyCorpus">Apply to Payload</button>
              <div class="help-text">Tip: Sets both <code>corpus</code> and <code>index_namespace</code> to enable per-corpus synonyms and indexing.</div>
            </div>
          </div>
        </div>

        <script>
          function ragApplyCorpusToPayload() {
            try {
              const input = document.getElementById('ragComplexSearch_corpus');
              const name = (input?.value || '').trim();
              const ta = document.getElementById('ragComplexSearch_payload');
              let obj = {};
              try { obj = JSON.parse(ta.value); } catch (e) { obj = {}; }
              if (name) {
                obj.corpus = name;
                obj.index_namespace = name;
              } else {
                // Clear on empty input
                delete obj.corpus;
                delete obj.index_namespace;
              }
              ta.value = JSON.stringify(obj, null, 2);
              Toast.success('Corpus applied to payload');
            } catch (e) {
              Toast.error('Failed to apply corpus: ' + (e?.message || e));
            }
          }
        </script>

        <h4>Server Presets</h4>
        <div class="form-group">
            <button class="api-button" id="btnRagServerRefresh">Refresh Presets</button>
            <select id="ragServerPreset_select" style="min-width: 240px;"></select>
            <button class="api-button" id="btnRagServerApply">Apply Selected Preset</button>
        </div>
        <script>
            async function refreshRagServerPresets() {
                try {
                    const resp = await apiClient.makeRequest('GET', '/api/v1/evaluations/rag/pipeline/presets');
                    const sel = document.getElementById('ragServerPreset_select');
                    sel.innerHTML = '';
                    const items = (resp && resp.items) ? resp.items : [];
                    items.forEach(item => {
                        const opt = document.createElement('option');
                        opt.value = item.name;
                        opt.textContent = item.name;
                        sel.appendChild(opt);
                    });
                    Toast.success(`Loaded ${items.length} presets`);
                } catch (e) {
                    alert('Failed to list presets: ' + (e?.message || e));
                }
            }
            async function applyRagServerPreset() {
                try {
                    const sel = document.getElementById('ragServerPreset_select');
                    const name = sel && sel.value ? sel.value : null;
                    if (!name) { alert('Select a preset first'); return; }
                    const resp = await apiClient.makeRequest('GET', `/api/v1/evaluations/rag/pipeline/presets/${encodeURIComponent(name)}`);
                    if (!(resp && resp.config)) { alert('Preset not found'); return; }
                    const cfg = resp.config;
                    const ta = document.getElementById('ragComplexSearch_payload');
                    let obj = {};
                    try { obj = JSON.parse(ta.value); } catch (e) { obj = {}; }
                    // Ensure default structure
                    obj.retrieval = obj.retrieval || { search_type: 'hybrid', hybrid_config: {}, vector_config: {}, fts_config: {} };
                    obj.reranking = obj.reranking || { enabled: true, strategies: ['similarity'], top_k: 10 };
                    // Map preset -> RAG payload
                    if (cfg.retriever) {
                        const sm = cfg.retriever.search_mode || cfg.retriever.search_type;
                        if (sm) {
                            // Normalize to our payload keywords
                            obj.retrieval.search_type = (sm === 'vector') ? 'semantic' : (sm === 'fts' ? 'fulltext' : 'hybrid');
                        }
                        if (cfg.retriever.hybrid_alpha !== undefined) {
                            obj.retrieval.hybrid_config = obj.retrieval.hybrid_config || {};
                            obj.retrieval.hybrid_config.alpha = cfg.retriever.hybrid_alpha;
                        }
                        if (cfg.retriever.top_k !== undefined) {
                            obj.retrieval.vector_config = obj.retrieval.vector_config || {};
                            obj.retrieval.vector_config.top_k = cfg.retriever.top_k;
                        }
                    }
                    if (cfg.reranker) {
                        // Map strategies
                        const strat = cfg.reranker.strategy;
                        if (strat) {
                            if (strat === 'flashrank') obj.reranking.strategies = ['similarity'];
                            else if (strat === 'cross_encoder') obj.reranking.strategies = ['cross_encoder'];
                            else obj.reranking.strategies = [String(strat)];
                        }
                        if (cfg.reranker.top_k !== undefined) obj.reranking.top_k = cfg.reranker.top_k;
                        if (cfg.reranker.model) obj.reranking.model = cfg.reranker.model;
                        obj.reranking.enabled = true;
                    }
                    // Generation model doesn’t directly map to search; skip or map to reranking.model if cross-encoder
                    ta.value = JSON.stringify(obj, null, 2);
                    Toast.success('Preset applied to RAG search payload.');
                } catch (e) {
                    alert('Failed to apply server preset: ' + (e?.message || e));
                }
            }
        </script>

        <div class="form-group" style="margin: 10px 0; padding: 10px; border: 1px dashed #ccc;">
          <h4>Reranking (Unified)</h4>
          <div class="columns">
            <div class="column">
              <label><input type="checkbox" id="ragComplex_enableRerank" checked> Enable Reranking</label>
            </div>
            <div class="column">
              <label for="ragComplex_rerankStrategy">Strategy:</label>
              <select id="ragComplex_rerankStrategy">
                <option value="flashrank">flashrank</option>
                <option value="cross_encoder">cross_encoder</option>
                <option value="hybrid">hybrid</option>
                <option value="llama_cpp">llama_cpp</option>
                <option value="llm_scoring">llm_scoring</option>
                <option value="two_tier">two_tier</option>
              </select>
            </div>
            <div class="column">
              <label for="ragComplex_rerankTopK">Top K:</label>
              <input type="number" id="ragComplex_rerankTopK" min="1" max="50" placeholder="e.g., 10" />
            </div>
          </div>
          <div class="columns" id="ragComplex_twoTierRow" style="display:none; margin-top:6px;">
            <div class="column">
              <label for="ragComplex_rerankMinProb">Min relevance prob:</label>
              <input type="number" id="ragComplex_rerankMinProb" min="0" max="1" step="0.01" placeholder="0.5" />
            </div>
            <div class="column">
              <label for="ragComplex_rerankSentinelMargin">Sentinel margin:</label>
              <input type="number" id="ragComplex_rerankSentinelMargin" min="0" max="1" step="0.01" placeholder="0.15" />
            </div>
          </div>
          <small class="text-muted">These map to unified keys: <code>enable_reranking</code>, <code>reranking_strategy</code>, <code>rerank_top_k</code>. Two-tier adds <code>rerank_min_relevance_prob</code> and <code>rerank_sentinel_margin</code>.</small>
        </div>

        <button class="api-button" data-req-section="ragComplexSearch" data-req-method="POST" data-req-path="/api/v1/rag/search" data-req-body-type="json">
            Send Request
        </button>

        <h3>cURL Command:</h3>
        <pre id="ragComplexSearch_curl">---</pre>

        <h3>Response:</h3>
        <pre id="ragComplexSearch_response">---</pre>
        <div class="form-group" style="margin-top:8px; display:flex; gap:8px; align-items:center;">
            <button class="api-button" id="btnRagComplexShowExplain">Show Explain (Agentic)</button>
            <span class="text-muted">Renders plan + spans when strategy=agentic (or provenance present)</span>
        </div>
        <div id="ragComplexSearch_explainPanel" class="mt-2"></div>
        <div id="ragComplexSearch_resultList" class="mt-2"></div>
    </div>

    <div class="endpoint-section" id="ragPipelines">
        <h2>
            <span class="endpoint-method get">GET</span>
            <span class="endpoint-path">/api/v1/evaluations/rag/pipeline/presets - Pipeline Presets</span>
        </h2>
        <p>List available pipeline presets for RAG evaluation and configuration.</p>

        <button class="api-button" data-req-section="ragPipelines" data-req-method="GET" data-req-path="/api/v1/evaluations/rag/pipeline/presets" data-req-body-type="none">
            List Presets
        </button>

        <h3>cURL Command:</h3>
        <pre id="ragPipelines_curl">---</pre>

        <h3>Response:</h3>
        <pre id="ragPipelines_response">---</pre>
    </div>

    <div class="endpoint-section" id="ragCapabilities">
        <h2>
            <span class="endpoint-method get">GET</span>
            <span class="endpoint-path">/api/v1/rag/capabilities - Get RAG Service Capabilities</span>
        </h2>
        <p>Get detailed information about available RAG service features and capabilities.</p>

        <button class="api-button" data-req-section="ragCapabilities" data-req-method="GET" data-req-path="/api/v1/rag/capabilities" data-req-body-type="none">
            Get Capabilities
        </button>

        <h3>cURL Command:</h3>
        <pre id="ragCapabilities_curl">---</pre>

        <h3>Response:</h3>
        <pre id="ragCapabilities_response">---</pre>
    </div>

    <div class="endpoint-section" id="ragHealth">
        <h2>
            <span class="endpoint-method get">GET</span>
            <span class="endpoint-path">/api/v1/rag/health - RAG Service Health</span>
        </h2>
        <p>Check RAG service health and status information.</p>

        <button class="api-button" data-req-section="ragHealth" data-req-method="GET" data-req-path="/api/v1/rag/health" data-req-body-type="none">
            Check Health
        </button>

        <h3>cURL Command:</h3>
        <pre id="ragHealth_curl">---</pre>

        <h3>Response:</h3>
        <pre id="ragHealth_response">---</pre>
    </div>

    <div class="endpoint-section" id="ragVlmBackends">
        <h2>
            <span class="endpoint-method get">GET</span>
            <span class="endpoint-path">/api/v1/rag/vlm/backends - VLM Backends</span>
        </h2>
        <p>List VLM backends and availability (endpoint discovered via Capabilities).</p>
        <div class="form-group">
            <button class="api-button" id="btnRagFetchVlmBackends">Fetch VLM Backends</button>
            <span id="ragVlmBackends_ep" class="text-muted" style="margin-left:8px; font-size: 0.9em;"></span>
        </div>
        <div class="columns">
            <div class="column">
                <h3>Backends</h3>
                <div id="ragVlmBackends_list" style="min-height: 40px; border:1px solid #ddd; padding:6px;"></div>
            </div>
            <div class="column">
                <h3>Raw JSON</h3>
                <pre id="ragVlmBackends_json">---</pre>
            </div>
        </div>
    </div>
</div>

<!-- migrated to js/rag.js
<script>
  // Implicit feedback sender
  async function sendImplicitRagFeedback(eventType, docId, rank, impression, query, corpus) {
    try {
      const payload = {
        event_type: eventType,
        doc_id: docId,
        rank: rank,
        impression_list: impression,
        query: query || null,
        corpus: corpus || null
      };
      await apiClient.makeRequest('POST', '/api/v1/rag/feedback/implicit', payload);
    } catch (e) {
      // Silent by default
      console.warn('implicit feedback failed', e?.message || e);
    }
  }

  function buildResultList(containerId, responsePreId, corpusInputId) {
    const cont = document.getElementById(containerId);
    const pre = document.getElementById(responsePreId);
    const corpusInput = document.getElementById(corpusInputId);
    if (!cont || !pre) return;
    const render = () => {
      let data = null;
      try {
        const text = pre.textContent || '';
        data = JSON.parse(text);
      } catch (e) {
        cont.innerHTML = '';
        return;
      }
      const docs = Array.isArray(data?.documents) ? data.documents : [];
      const query = data?.query || '';
      const corpus = (corpusInput && corpusInput.value) ? corpusInput.value.trim() : (data?.metadata?.index_namespace || data?.index_namespace || null);
      if (!docs.length) { cont.innerHTML = ''; return; }
      const impression = docs.map((d) => d.id);
      const container = document.createElement('div');
      container.className = 'card-list';
      docs.slice(0, 10).forEach((d, i) => {
        const card = document.createElement('div');
        card.className = 'card';
        const header = document.createElement('div');
        header.className = 'card-header';
        header.textContent = `[${i+1}] ${d.id}  (score=${(d.score||0).toFixed(3)})`;
        header.style.cursor = 'pointer';
        const body = document.createElement('div');
        body.className = 'card-body';
        body.style.display = 'none';
        const content = document.createElement('pre');
        content.textContent = (d.content || '').slice(0, 1200);
        const copyBtn = document.createElement('button');
        copyBtn.className = 'btn btn-sm';
        copyBtn.textContent = 'Copy snippet';
        copyBtn.addEventListener('click', async (ev) => {
          ev.stopPropagation();
          const ok = await Utils.copyToClipboard(d.content || '');
          if (ok) Toast.success('Copied snippet'); else Toast.error('Copy failed');
          sendImplicitRagFeedback('copy', d.id, i+1, impression, query, corpus);
        });
        body.appendChild(content);
        body.appendChild(copyBtn);
        header.addEventListener('click', () => {
          const show = body.style.display === 'none';
          body.style.display = show ? 'block' : 'none';
          sendImplicitRagFeedback('expand', d.id, i+1, impression, query, corpus);
        });
        card.appendChild(header);
        card.appendChild(body);
        card.addEventListener('click', () => {
          sendImplicitRagFeedback('click', d.id, i+1, impression, query, corpus);
        });
        container.appendChild(card);
      });
      cont.innerHTML = '';
      cont.appendChild(container);
    };
    // Observe changes to pre element
    const observer = new MutationObserver(render);
    observer.observe(pre, { characterData: true, childList: true, subtree: true });
  }

  // Wire up observers for Simple and Complex forms
  (function() {
    buildResultList('ragSimpleSearch_resultList', 'ragSimpleSearch_response', 'ragSimpleSearch_corpus');
    buildResultList('ragComplexSearch_resultList', 'ragComplexSearch_response', 'ragComplexSearch_corpus');
  })();

  // Explain panel renderers
  function _renderExplainFromPre(preId, mountId) {
    try {
      const pre = document.getElementById(preId);
      const mount = document.getElementById(mountId);
      if (!pre || !mount) return;
      let data = null;
      try { data = JSON.parse(pre.textContent || ''); } catch (e) { data = null; }
      if (!data || !data.metadata) { mount.textContent = 'No metadata available.'; return; }
      const md = data.metadata;
      if (typeof window.renderAgenticExplainPanel === 'function') {
        window.renderAgenticExplainPanel(md, mount);
      } else {
        mount.textContent = 'Explain renderer not available.';
      }
    } catch (e) {
      console.error('Explain render failed', e);
    }
  }
  function ragSimpleShowExplain() { _renderExplainFromPre('ragSimpleSearch_response', 'ragSimpleSearch_explainPanel'); }
  function ragComplexShowExplain() { _renderExplainFromPre('ragComplexSearch_response', 'ragComplexSearch_explainPanel'); }
</script> -->

<!-- RAG Embeddings Tab -->
<div id="tabRAGEmbeddings" class="tab-content">
    <div class="endpoint-section">
    <h2>RAG Embeddings Management</h2>
    <p>The embeddings for RAG are managed through the dedicated Embeddings service. Please use the Embeddings tab for creating and managing embeddings.</p>

    <h3>Server Presets (View)</h3>
    <p>Load a server preset to inspect its retrieval/reranking/generation blocks while working on embeddings.</p>
    <div class="form-group">
        <button class="api-button" onclick="ragEmbRefreshPresets()">Refresh Presets</button>
        <select id="ragEmbPreset_select" style="min-width: 260px;"></select>
        <button class="api-button" onclick="ragEmbLoadPreset()">Load Preset</button>
    </div>
    <h4>Preset Config</h4>
    <pre id="ragEmbPreset_view">(none loaded)</pre>
    <!-- migrated to js/rag.js
    <script>
        async function ragEmbRefreshPresets() {
            try {
                const resp = await apiClient.makeRequest('GET', '/api/v1/evaluations/rag/pipeline/presets');
                const sel = document.getElementById('ragEmbPreset_select');
                sel.innerHTML = '';
                const items = (resp && resp.items) ? resp.items : [];
                items.forEach(item => {
                    const opt = document.createElement('option');
                    opt.value = item.name; opt.textContent = item.name;
                    sel.appendChild(opt);
                });
                Toast.success(`Loaded ${items.length} presets`);
            } catch (e) {
                Toast.error('Failed to list presets: ' + (e?.message || e));
            }
        }
        async function ragEmbLoadPreset() {
            try {
                const sel = document.getElementById('ragEmbPreset_select');
                const name = sel && sel.value ? sel.value : null;
                if (!name) { alert('Select a preset first'); return; }
                const resp = await apiClient.makeRequest('GET', `/api/v1/evaluations/rag/pipeline/presets/${encodeURIComponent(name)}`);
                const pre = document.getElementById('ragEmbPreset_view');
                if (resp && resp.config) {
                    pre.textContent = JSON.stringify(resp.config, null, 2);
                    Toast.success('Preset loaded');
                } else {
                    pre.textContent = '(not found)';
                }
            } catch (e) {
                Toast.error('Failed to load preset: ' + (e?.message || e));
            }
        }
    </script> -->

        <div class="form-group mt-3">
            <h3>Quick Links</h3>
            <div class="btn-group">
                <button class="btn btn-primary" onclick="webUI.activateTopTab(document.querySelector('[data-toptab=\"embeddings\"]'))">
                    Go to Embeddings Tab
                </button>
            </div>
        </div>
    </div>
</div>
